/***************************************************************************/
/*                                                                         */
/*  ftcsbits.c                                                             */
/*                                                                         */
/*    FreeType sbits manager (body).                                       */
/*                                                                         */
/*  Copyright 2000-2001, 2002, 2003, 2004, 2005, 2006, 2009 by             */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/


#include <ft2build.h>
#include FT_CACHE_H
#include "ftcsbits.h"
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include FT_ERRORS_H

#include "ftccback.h"
#include "ftcerror.h"

#undef  FT_COMPONENT
#define FT_COMPONENT trace_cache


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                     SBIT CACHE NODES                          *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


static FT_Error
ftc_sbit_copy_bitmap(FTC_SBit sbit,
                     FT_Bitmap* bitmap,
                     FT_Memory memory)
{
    FT_Error error;
    FT_Int pitch = bitmap->pitch;
    FT_ULong size;


    if (pitch < 0)
        pitch = -pitch;

    size = (FT_ULong)(pitch * bitmap->rows);

    if (!FT_ALLOC(sbit->buffer, size))
        FT_MEM_COPY(sbit->buffer, bitmap->buffer, size);

    return error;
}


FT_LOCAL_DEF(void)
ftc_snode_free(FTC_Node ftcsnode,
               FTC_Cache cache)
{
    FTC_SNode snode = (FTC_SNode)ftcsnode;
    FTC_SBit sbit = snode->sbits;
    FT_UInt count = snode->count;
    FT_Memory memory = cache->memory;


    for (; count > 0; sbit++, count--)
        FT_FREE(sbit->buffer);

    FTC_GNode_Done(FTC_GNODE(snode), cache);

    FT_FREE(snode);
}


FT_LOCAL_DEF(void)
FTC_SNode_Free(FTC_SNode snode,
               FTC_Cache cache)
{
    ftc_snode_free(FTC_NODE(snode), cache);
}


/*
 *  This function tries to load a small bitmap within a given FTC_SNode.
 *  Note that it returns a non-zero error code _only_ in the case of
 *  out-of-memory condition.  For all other errors (e.g., corresponding
 *  to a bad font file), this function will mark the sbit as `unavailable'
 *  and return a value of 0.
 *
 *  You should also read the comment within the @ftc_snode_compare
 *  function below to see how out-of-memory is handled during a lookup.
 */
static FT_Error
ftc_snode_load(FTC_SNode snode,
               FTC_Manager manager,
               FT_UInt gindex,
               FT_ULong* asize)
{
    FT_Error error;
    FTC_GNode gnode = FTC_GNODE(snode);
    FTC_Family family = gnode->family;
    FT_Memory memory = manager->memory;
    FT_Face face;
    FTC_SBit sbit;
    FTC_SFamilyClass clazz;


    if ((FT_UInt)(gindex - gnode->gindex) >= snode->count)
    {
        FT_ERROR(("ftc_snode_load: invalid glyph index"));
        return FTC_Err_Invalid_Argument;
    }

    sbit = snode->sbits + (gindex - gnode->gindex);
    clazz = (FTC_SFamilyClass)family->clazz;

    sbit->buffer = 0;

    error = clazz->family_load_glyph(family, gindex, manager, &face);
    if (error)
        goto BadGlyph;

    {
        FT_Int temp;
        FT_GlyphSlot slot = face->glyph;
        FT_Bitmap* bitmap = &slot->bitmap;
        FT_Pos xadvance, yadvance;      /* FT_GlyphSlot->advance.{x|y} */


        if (slot->format != FT_GLYPH_FORMAT_BITMAP)
        {
            FT_TRACE0(("ftc_snode_load:"
                       " glyph loaded didn't return a bitmap\n"));
            goto BadGlyph;
        }

        /* Check that our values fit into 8-bit containers!       */
        /* If this is not the case, our bitmap is too large       */
        /* and we will leave it as `missing' with sbit.buffer = 0 */

#define CHECK_CHAR(d)  (temp = (FT_Char)d, temp == d)
#define CHECK_BYTE(d)  (temp = (FT_Byte)d, temp == d)

        /* horizontal advance in pixels */
        xadvance = (slot->advance.x + 32) >> 6;
        yadvance = (slot->advance.y + 32) >> 6;

        if (!CHECK_BYTE(bitmap->rows) ||
            !CHECK_BYTE(bitmap->width) ||
            !CHECK_CHAR(bitmap->pitch) ||
            !CHECK_CHAR(slot->bitmap_left) ||
            !CHECK_CHAR(slot->bitmap_top) ||
            !CHECK_CHAR(xadvance) ||
            !CHECK_CHAR(yadvance))
            goto BadGlyph;

        sbit->width = (FT_Byte)bitmap->width;
        sbit->height = (FT_Byte)bitmap->rows;
        sbit->pitch = (FT_Char)bitmap->pitch;
        sbit->left = (FT_Char)slot->bitmap_left;
        sbit->top = (FT_Char)slot->bitmap_top;
        sbit->xadvance = (FT_Char)xadvance;
        sbit->yadvance = (FT_Char)yadvance;
        sbit->format = (FT_Byte)bitmap->pixel_mode;
        sbit->max_grays = (FT_Byte)(bitmap->num_grays - 1);

        /* copy the bitmap into a new buffer -- ignore error */
        error = ftc_sbit_copy_bitmap(sbit, bitmap, memory);

        /* now, compute size */
        if (asize)
            *asize = FT_ABS(sbit->pitch) * sbit->height;

    } /* glyph loading successful */

    /* ignore the errors that might have occurred --   */
    /* we mark unloaded glyphs with `sbit.buffer == 0' */
    /* and `width == 255', `height == 0'               */
    /*                                                 */
    if (error && error != FTC_Err_Out_Of_Memory)
    {
BadGlyph:
        sbit->width = 255;
        sbit->height = 0;
        sbit->buffer = NULL;
        error = 0;
        if (asize)
            *asize = 0;
    }

    return error;
}


FT_LOCAL_DEF(FT_Error)
FTC_SNode_New(FTC_SNode * psnode,
              FTC_GQuery gquery,
              FTC_Cache cache)
{
    FT_Memory memory = cache->memory;
    FT_Error error;
    FTC_SNode snode = NULL;
    FT_UInt gindex = gquery->gindex;
    FTC_Family family = gquery->family;

    FTC_SFamilyClass clazz = FTC_CACHE__SFAMILY_CLASS(cache);
    FT_UInt total;


    total = clazz->family_get_count(family, cache->manager);
    if (total == 0 || gindex >= total)
    {
        error = FT_Err_Invalid_Argument;
        goto Exit;
    }

    if (!FT_NEW(snode))
    {
        FT_UInt count, start;


        start = gindex - (gindex % FTC_SBIT_ITEMS_PER_NODE);
        count = total - start;
        if (count > FTC_SBIT_ITEMS_PER_NODE)
            count = FTC_SBIT_ITEMS_PER_NODE;

        FTC_GNode_Init(FTC_GNODE(snode), start, family);

        snode->count = count;

        error = ftc_snode_load(snode,
                               cache->manager,
                               gindex,
                               NULL);
        if (error)
        {
            FTC_SNode_Free(snode, cache);
            snode = NULL;
        }
    }

Exit:
    *psnode = snode;
    return error;
}


FT_LOCAL_DEF(FT_Error)
ftc_snode_new(FTC_Node * ftcpsnode,
              FT_Pointer ftcgquery,
              FTC_Cache cache)
{
    FTC_SNode* psnode = (FTC_SNode*)ftcpsnode;
    FTC_GQuery gquery = (FTC_GQuery)ftcgquery;


    return FTC_SNode_New(psnode, gquery, cache);
}


FT_LOCAL_DEF(FT_Offset)
ftc_snode_weight(FTC_Node ftcsnode,
                 FTC_Cache cache)
{
    FTC_SNode snode = (FTC_SNode)ftcsnode;
    FT_UInt count = snode->count;
    FTC_SBit sbit = snode->sbits;
    FT_Int pitch;
    FT_Offset size;

    FT_UNUSED(cache);


    FT_ASSERT(snode->count <= FTC_SBIT_ITEMS_PER_NODE);

    /* the node itself */
    size = sizeof(*snode);

    for (; count > 0; count--, sbit++)
    {
        if (sbit->buffer)
        {
            pitch = sbit->pitch;
            if (pitch < 0)
                pitch = -pitch;

            /* add the size of a given glyph image */
            size += pitch * sbit->height;
        }
    }

    return size;
}


#if 0

FT_LOCAL_DEF(FT_Offset)
FTC_SNode_Weight(FTC_SNode snode)
{
    return ftc_snode_weight(FTC_NODE(snode), NULL);
}

#endif /* 0 */


FT_LOCAL_DEF(FT_Bool)
ftc_snode_compare(FTC_Node ftcsnode,
                  FT_Pointer ftcgquery,
                  FTC_Cache cache)
{
    FTC_SNode snode = (FTC_SNode)ftcsnode;
    FTC_GQuery gquery = (FTC_GQuery)ftcgquery;
    FTC_GNode gnode = FTC_GNODE(snode);
    FT_UInt gindex = gquery->gindex;
    FT_Bool result;


    result = FT_BOOL(gnode->family == gquery->family &&
                     (FT_UInt)(gindex - gnode->gindex) < snode->count);
    if (result)
    {
        /* check if we need to load the glyph bitmap now */
        FTC_SBit sbit = snode->sbits + (gindex - gnode->gindex);


        /*
         *  The following code illustrates what to do when you want to
         *  perform operations that may fail within a lookup function.
         *
         *  Here, we want to load a small bitmap on-demand; we thus
         *  need to call the `ftc_snode_load' function which may return
         *  a non-zero error code only when we are out of memory (OOM).
         *
         *  The correct thing to do is to use @FTC_CACHE_TRYLOOP and
         *  @FTC_CACHE_TRYLOOP_END in order to implement a retry loop
         *  that is capable of flushing the cache incrementally when
         *  an OOM errors occur.
         *
         *  However, we need to `lock' the node before this operation to
         *  prevent it from being flushed within the loop.
         *
         *  When we exit the loop, we unlock the node, then check the `error'
         *  variable.  If it is non-zero, this means that the cache was
         *  completely flushed and that no usable memory was found to load
         *  the bitmap.
         *
         *  We then prefer to return a value of 0 (i.e., NO MATCH).  This
         *  ensures that the caller will try to allocate a new node.
         *  This operation consequently _fail_ and the lookup function
         *  returns the appropriate OOM error code.
         *
         *  Note that `buffer == NULL && width == 255' is a hack used to
         *  tag `unavailable' bitmaps in the array.  We should never try
         *  to load these.
         *
         */

        if (sbit->buffer == NULL && sbit->width != 255)
        {
            FT_ULong size;
            FT_Error error;


            ftcsnode->ref_count++; /* lock node to prevent flushing */
                                   /* in retry loop                 */

            FTC_CACHE_TRYLOOP(cache)
            {
                error = ftc_snode_load(snode, cache->manager, gindex, &size);
            }
            FTC_CACHE_TRYLOOP_END();

            ftcsnode->ref_count--; /* unlock the node */

            if (error)
                result = 0;
            else
                cache->manager->cur_weight += size;
        }
    }

    return result;
}


FT_LOCAL_DEF(FT_Bool)
FTC_SNode_Compare(FTC_SNode snode,
                  FTC_GQuery gquery,
                  FTC_Cache cache)
{
    return ftc_snode_compare(FTC_NODE(snode), gquery, cache);
}


/* END */